/*****************************************************************************
**	 part of SMSE2K
**   This is a port of the z80 emulator from MAME to work as a class-based thinger.
**
**   Revision history
**   10 / 18 / 2009 - started porting to the SMSE2K CPU interface
**   11 / 08 / 2009 - port is nearing completion
**   12 / 06 / 2009 - bug checking in progress with test drivers
**	 12 / 07 / 2009 - now works 100% due to fixes in mametypedefs.h, still fixing scheduler problems
**   12 / 17 / 2009 - Z80 code might have problems due to the various versions that the emulator is built on
**
**   TODO:
**   - Clean up the CPU code to use less member functions
**   - Optimize out the ass
**   - Throw out the jumptables and replace it with something better
**   - Check for buegs (and there are lots of them!)
**
******************************************************************************
 *   z80.c
 *   Portable Z80 emulator V3.5
 *
 *   Copyright Juergen Buchmueller, all rights reserved.
 *
 *   - This source code is released as freeware for non-commercial purposes.
 *   - You are free to use and redistribute this code in modified or
 *     unmodified form, provided you list me in the credits.
 *   - If you modify this source code, you must add a notice to each modified
 *     source file that it has been changed.  If you're a nice person, you
 *     will clearly mark each change too.  :)
 *   - If you wish to use this for commercial purposes, please contact me at
 *     pullmoll@t-online.de
 *   - The author of this copywritten work reserves the right to change the
 *     terms of its usage and license at any time, including retroactively
 *   - This entire notice must remain in the source code.
 *****************************************************************************/

#include <string.h>
#include "smse2kincludes.h"
#include "CPUFactory.h"
#include "Z80_SMSE.h"

//////////////////////////////////////////////////////
// Z80_Ops.h contains the complete operand table.   //
//////////////////////////////////////////////////////
#include "Z80_Ops.h"


int CZ80::Init(int clk) {
	BuildOptables();

	this->clock = clk;

	//if (z80->daisy)
	//	z80daisy_reset(z80->daisy);
	int i, p;
	int oldval, newval, val;
	UINT8 *padd, *padc, *psub, *psbc;
	SZHVC_add = new UINT8[2*256*256];
	SZHVC_sub = new UINT8[2*256*256];

	padd = &SZHVC_add[	0*256];
	padc = &SZHVC_add[256*256];
	psub = &SZHVC_sub[	0*256];
	psbc = &SZHVC_sub[256*256];
	for (oldval = 0; oldval < 256; oldval++) {
			for (newval = 0; newval < 256; newval++)
			{
				/* add or adc w/o carry set */
				val = newval - oldval;
				*padd = (newval) ? ((newval & 0x80) ? SF : 0) : ZF;
				*padd |= (newval & (YF | XF));	/* undocumented flag bits 5+3 */
				if( (newval & 0x0f) < (oldval & 0x0f) ) *padd |= HF;
				if( newval < oldval ) *padd |= CF;
				if( (val^oldval^0x80) & (val^newval) & 0x80 ) *padd |= VF;
				padd++;

				/* adc with carry set */
				val = newval - oldval - 1;
				*padc = (newval) ? ((newval & 0x80) ? SF : 0) : ZF;
				*padc |= (newval & (YF | XF));	/* undocumented flag bits 5+3 */
				if( (newval & 0x0f) <= (oldval & 0x0f) ) *padc |= HF;
				if( newval <= oldval ) *padc |= CF;
				if( (val^oldval^0x80) & (val^newval) & 0x80 ) *padc |= VF;
				padc++;

				/* cp, sub or sbc w/o carry set */
				val = oldval - newval;
				*psub = NF | ((newval) ? ((newval & 0x80) ? SF : 0) : ZF);
				*psub |= (newval & (YF | XF));	/* undocumented flag bits 5+3 */
				if( (newval & 0x0f) > (oldval & 0x0f) ) *psub |= HF;
				if( newval > oldval ) *psub |= CF;
				if( (val^oldval) & (oldval^newval) & 0x80 ) *psub |= VF;
				psub++;

				/* sbc with carry set */
				val = oldval - newval - 1;
				*psbc = NF | ((newval) ? ((newval & 0x80) ? SF : 0) : ZF);
				*psbc |= (newval & (YF | XF));	/* undocumented flag bits 5+3 */
				if( (newval & 0x0f) >= (oldval & 0x0f) ) *psbc |= HF;
				if( newval >= oldval ) *psbc |= CF;
				if( (val^oldval) & (oldval^newval) & 0x80 ) *psbc |= VF;
				psbc++;
			}
		}

	for (i = 0; i < 256; i++)
	{
		p = 0;
		if( i&0x01 ) ++p;
		if( i&0x02 ) ++p;
		if( i&0x04 ) ++p;
		if( i&0x08 ) ++p;
		if( i&0x10 ) ++p;
		if( i&0x20 ) ++p;
		if( i&0x40 ) ++p;
		if( i&0x80 ) ++p;
		SZ[i] = i ? i & SF : ZF;
		SZ[i] |= (i & (YF | XF));		/* undocumented flag bits 5+3 */
		SZ_BIT[i] = i ? i & SF : ZF | PF;
		SZ_BIT[i] |= (i & (YF | XF));	/* undocumented flag bits 5+3 */
		SZP[i] = SZ[i] | ((p & 1) ? 0 : PF);
		SZHV_inc[i] = SZ[i];
		if( i == 0x80 ) SZHV_inc[i] |= VF;
		if( (i & 0x0f) == 0x00 ) SZHV_inc[i] |= HF;
		SZHV_dec[i] = SZ[i] | NF;
		if( i == 0x7f ) SZHV_dec[i] |= VF;
		if( (i & 0x0f) == 0x0f ) SZHV_dec[i] |= HF;
	}

	MEMPTR=PCD;

	/* Reset registers to their initial values */

	AF = 0;
	BC = 0;
	HL = 0;
	IX = IY = 0xffff; /* IX and IY are FFFF after a reset! */
	F = ZF;			/* Zero flag is set */

	Reset();
	return 0;
}

void CZ80::Reset() {

	PC = 0x0000;
	i = 0;
	r = 0;
	r2 = 0;
	nmi_state = CLEAR_LINE;
	nmi_pending = FALSE;
	irq_state = CLEAR_LINE;
	after_ei = FALSE;
	

	//if (z80->daisy)
	//	z80daisy_reset(z80->daisy);

	MEMPTR=PCD;
}

void CZ80::SetRegister(const char* regname, UINT64 val) {
	val &= 0xffff;	
	if (!strcmpi(regname,"a"))  A = val;
	else if (!strcmpi(regname,"sp")) SP = val;
	else if (!strcmpi(regname,"hl")) HL = val;
	else if (!strcmpi(regname,"pc")) PC = val;
}

int CZ80::GetInterruptStatus(int line) {
	if (line)	// NMI
		return nmi_state;
	else		// normal IRQ
		return irq_state;
}

void CZ80::Shutdown() {
	if (SZHVC_add) delete[] SZHVC_add;
	SZHVC_add = NULL;
	if (SZHVC_sub) delete[] SZHVC_sub;
	SZHVC_sub = NULL;
}

void CZ80::TakeInterrupt() {
	
	PRVPC = -1;

	// Leave halt state
	LEAVE_HALT(anusfest);

	iff1 = iff2 = 0;

	//Msg("Z80 Took IRQ\n");
	// We specified a vector, so we don't need to bother with daisychains. Carry out the IRQ
	switch( im ) {

		case 0: // IM 0 (vectored). commonly used on home computers
			switch (irq_vector & 0xff0000)
			{
			case 0xcd0000: // Call
				PUSH(z80, pc);
				PCD = irq_vector & 0xffff;
				 // CALL $xxxx + 'interrupt latency' cycles
				icount -= cc_op[0xcd] + cc_ex[0xff];
				break;
			case 0xc30000:	// jump
				PCD = irq_vector & 0xffff;
				// JP $xxxx + 2 cycles
				icount -= cc_op[0xc3] + cc_ex[0xff];
				break;
			default:		// rst (or other opcodes?)
				PUSH(z80, pc);
				PCD = irq_vector & 0x0038;
				/* RST $xx + 2 cycles */
				icount -= cc_op[0xff] + cc_ex[0xff];
				break;
			}
			break;

		case 1: // IM 1, rst 38h. most z80 programs run in this mode
			PUSH(this, pc);
			PC = 0x0038;
	//		Msg("IM level 1: RST 38h\n");
			icount -= cc_op[0xff] + cc_ex[0xff];
			break;

		default:
			Msg("Emulation error in Z80.cpp: Unimplemented interrupt mode: %d\n",im);
			break;
	}

	this->irq_state = CLEAR_LINE; // Acknowledge the interrupt
}

int CZ80::Exec(int cycles) {
	icount = cycles;
	/* check for NMIs on the way in; they can only be set externally */
	/* via timers, and can't be dynamically enabled, so it is safe */
	/* to just check here */
	if (nmi_pending)
	{
		//Msg("Z80 received NMI\n");
		PRVPC = -1;			/* there isn't a valid previous program counter */
		LEAVE_HALT(z80);			/* Check if processor was halted */

		iff1 = 0;
		PUSH(z80, pc);
		PCD = 0x0066;
		MEMPTR=PCD;
		icount -= 11;
		
		// acknowledge the NMI if it's anything other than an assert
		if (nmi_state != ASSERT_LINE) {
			nmi_state = CLEAR_LINE;
			nmi_pending = FALSE;
		}
		

	}

	do
	{
		/* check for IRQs before each instruction */
		if (irq_state != CLEAR_LINE && iff1 && !after_ei) TakeInterrupt();
		after_ei = FALSE;

		PRVPC = PCD;

		r++;
		
		UINT32 opcode = ROP();		// Get opcode

		// Debug Stuff
	//	Msg("Z80 PC = 0x%p (opcode %02X) [ Debugging, press key to step in]\n",PC-1,opcode);
	//	getchar();

		CC(derp, op, opcode);		// Adjust cycle count
		(this->*optable[opcode])(); // Jump off to the opcode

		
	} while (icount > 0);

	return cycles - icount;
}

int CZ80::Burn(int cycles) {

	if( cycles > 0 ) {
			/* NOP takes 4 cycles per instruction */
			int n = (cycles + 3) / 4;
			r += n;
			icount -= 4 * n;
	}

	return 0;
}

void CZ80::Interrupt(int line, int state) {
	Interrupt(line,state,0);
}

void CZ80::Interrupt(int line, int state, int vector) {

	this->irq_vector = vector;
	if (line) {	// NMI
		
		if (nmi_state == CLEAR_LINE && state != CLEAR_LINE) {
			nmi_pending = TRUE;
			nmi_state = state;
		//	Msg("NMI now pending\n");
		}
		//else
		//	Msg("NMI received but line is not cleared. Line status %d, our status %d\n", nmi_state, state);
		//Msg("NMI line updated\n");
	} else {		// normal IRQ
		irq_state = state;
	}
}

void CZ80::AssignMMU(int slot, CBaseMMU* mmu) {
	if (slot) // anything not zero we'll count as I/O space
		io_space = mmu;
	else
		program_space = mmu;
}

CPU_FACTORY("Z80",1,CZ80);

// TODO: Kabuki encrypted CPU